﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using wChess.ChessPieces;

namespace wChess
{
    public class ChessBoard
    {
        /// <summary>
        /// 玩家走棋标记，为true时红方走，false时黑方走
        /// </summary>
        public static bool PlayToken = true;

        /// <summary>
        /// 棋盘宽度（以格子为单位）
        /// </summary>
        public const int BOARD_META_WIDTH = 9;

        /// <summary>
        /// 棋盘高度（以格子为单位）
        /// </summary>
        public const int BOARD_META_HEIGHT = 10;

        /// <summary>
        /// 线条宽度
        /// </summary>
        public const int LINE_WEIGHT = 2;

        private float _BoradWidth;
        /// <summary>
        /// 棋盘宽度（以像素为单位）
        /// </summary>
        public float BoradWidth
        {
            get
            {
                return _BoradWidth;
            }
            set
            {
                _BoradWidth = value;
                sW = (BoradWidth - LINE_WEIGHT * BOARD_META_WIDTH) / (BOARD_META_WIDTH);
            }
        }

        private float _BoradHeight;
        /// <summary>
        /// 棋盘高度（以像素为单位）
        /// </summary>
        public float BoardHeight
        {
            get
            {
                return _BoradHeight;
            }
            set
            {
                _BoradHeight = value;
                sH = (BoardHeight - LINE_WEIGHT * BOARD_META_HEIGHT) / (BOARD_META_HEIGHT);
            }
        }

        /// <summary>
        /// 棋子集合
        /// </summary>
        public static List<ChessPiece> Pieces = new List<ChessPiece>();

        /// <summary>
        /// 格子宽度（以像素为单位）
        /// </summary>
        public static float sW;

        /// <summary>
        /// 格子高度（以像素为单位）
        /// </summary>
        public static float sH;

        /// <summary>
        /// 棋盘矩阵
        /// </summary>
        public static byte[,] Matrix = new byte[BOARD_META_HEIGHT, BOARD_META_WIDTH];


        private TEAM firstTeam = TEAM.RED;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="w">棋盘宽度</param>
        /// <param name="h">棋盘高度</param>
        /// <param name="first">先走棋的一方（默认红方先走）</param>
        public ChessBoard(float w, float h, TEAM first = TEAM.RED)
        {
            BoradWidth = w;
            BoardHeight = h;
            firstTeam = first;

            ResetGame();
        }

        /// <summary>
        /// 初始化PlayToken
        /// </summary>
        private void InitPlayToken()
        {
            switch (firstTeam)
            {
                case TEAM.RED:
                    PlayToken = true;
                    break;
                case TEAM.BLACK:
                    PlayToken = false;
                    break;
            }
        }
        
        /// <summary>
        /// 初始化游戏
        /// </summary>
        public void ResetGame()
        {
            ResetMatrix();
            InitPlayToken();
            InitPieces();
        }

        /// <summary>
        /// 清空棋盘矩阵
        /// </summary>
        private static void ResetMatrix()
        {
            for (int i = 0; i < BOARD_META_HEIGHT; i++)
                for (int j = 0; j < BOARD_META_WIDTH; j++)
                {
                    Matrix[i, j] = 0;
                }
        }

        private static Stack<ChessPiece> _HistoryStack = new Stack<ChessPiece>();

        /// <summary>
        /// 当前棋局中所有走过的棋子记录
        /// </summary>
        public static Stack<ChessPiece> HistoryPieces
        {
            get
            {
                return _HistoryStack;
            }
            set
            {
                _HistoryStack = value;
            }
        }

        /// <summary>
        /// 更新走棋属性标记
        /// </summary>
        public static void UpdatePlayToken()
        {
            PlayToken = !PlayToken;
        }

        /// <summary>
        /// 依次回滚棋局（悔棋）
        /// </summary>
        /// <returns>能否回滚</returns>
        public static bool RollBack()
        {
            bool r = false;
            ChessPiece p = null;

            if (HistoryPieces.Count > 0)
                p = HistoryPieces.Pop();
            
            if (p != null)
            {
                p.RollBack();
                r = true;
            }
            return r;
        }

        /// <summary>
        /// 判断有没有明将
        /// </summary>
        /// <returns></returns>
        public static bool CheckMingJiang()
        {
            List<ChessPiece> list = new List<ChessPiece>();
            foreach (var p in Pieces)
            {
                if (p.Name == "将" || p.Name == "帅")
                    list.Add(p);
            }
            if (list[0].FixedMetaPosition.X != list[1].FixedMetaPosition.X)
                return false;
            ChessPiece upPiece = list[0].FixedMetaPosition.Y < list[1].FixedMetaPosition.Y ? list[0] : list[1];
            ChessPiece downPiece = list[0].FixedMetaPosition.Y > list[1].FixedMetaPosition.Y ? list[0] : list[1];
            for (int i = upPiece.FixedMetaPosition.Y + 1; i < downPiece.FixedMetaPosition.Y; i++)
            {
                if (Matrix[i, upPiece.FixedMetaPosition.X] == 1)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 检测当前棋局有没有将军危险
        /// </summary>
        /// <returns>返回被将军的一方的帅</returns>
        public static List<ChessPiece> CheckCheck()
        {
            List<ChessPiece> r = new List<ChessPiece>();
            foreach (var p in Pieces)
            {
                //如果该棋子已经被标记为“假吃掉”，则不再判断它的威胁
                if (p.IsVirtualEaten)
                    continue;
                foreach (var s in p.NextSteps)
                {
                    var ps = ChessBoard.GetPieceFromMetaPos(s.X, s.Y);
                    if (ps.Count == 0)
                        continue;
                    if (ps[0].Name == "将" || ps[0].Name == "帅")
                    {
                        r.Add(ps[0]);
                    }
                }
            }
            return r;
        }

        /// <summary>
        /// 检查该走棋的一方有没有被将军
        /// </summary>
        /// <returns></returns>
        private static bool IsMovingSideChecked()
        {
            bool r = false;
            var beChecked = CheckCheck();
            if (beChecked.Count == 0)
                return r;
            if (PlayToken == true)
            {
                foreach (var p in beChecked)
                {
                    if (p.Team == TEAM.RED)
                    {
                        r = true;
                        break;
                    }
                }
            }
            else
            {
                foreach (var p in beChecked)
                {
                    if (p.Team == TEAM.BLACK)
                    {
                        r = true;
                        break;
                    }
                }
            }
            return r;
        }

        /// <summary>
        /// 检查该走棋的一方可以移动的着法，返回的集合元素个数为0，则代表该方已经无路可走，输了
        /// </summary>
        /// <returns>该走棋的一方可以移动的着法集合</returns>
        public static List<Tuple<ChessPiece, Point>> CheckAvailableSteps()
        {
            List<Tuple<ChessPiece, Point>> r = new List<Tuple<ChessPiece, Point>>();
            foreach (var p in Pieces)
            {
                if (PlayToken == true && p.Team == TEAM.BLACK)
                    continue;
                if (PlayToken == false && p.Team == TEAM.RED)
                    continue;
                foreach (var next in p.NextSteps)
                {
                    var oldSrc = p.FixedMetaPosition;
                    var oldNext = Matrix[next.Y, next.X];

                    //首先假设该棋子走到新位置
                    Matrix[oldSrc.Y, oldSrc.X] = 0;
                    Matrix[next.Y, next.X] = 1;
                    var nextPiece = GetPieceFromMetaPos(next.X, next.Y);
                    if (nextPiece != null && nextPiece.Count == 1)
                    {
                        nextPiece[0].IsVirtualEaten = true;
                    }
                    p.FixedMetaPosition = next;

                    //然后检查此时该走棋的一方还有没有被将军，如果没有被将军，则说明这种走法可以有效破解对方的将军
                    if(IsMovingSideChecked()==false)
                    {
                        r.Add(new Tuple<ChessPiece, Point>(p, next));
                    }

                    //最后把刚才假设移动到新位置的棋子恢复到原位置
                    if (nextPiece != null && nextPiece.Count == 1)
                    {
                        nextPiece[0].IsVirtualEaten = false;
                    }
                    Matrix[oldSrc.Y, oldSrc.X] = 1;
                    Matrix[next.Y, next.X] = oldNext;
                    p.FixedMetaPosition = oldSrc;
                }
            }
            return r;
        }

        /// <summary>
        /// 交换红黑双方的位置
        /// 原理：对称改变双方棋子的横纵坐标
        /// </summary>
        public void ExchangeSide()
        {
            if (Pieces == null)
                return;
            
            foreach (var p in Pieces)
            {
                Point exFixedPoint = new Point(BOARD_META_WIDTH - 1 - p.FixedMetaPosition.X, BOARD_META_HEIGHT - 1 - p.FixedMetaPosition.Y);

                //交换棋盘矩阵对应的元素
                var temp = Matrix[p.FixedMetaPosition.Y, p.FixedMetaPosition.X];
                Matrix[p.FixedMetaPosition.Y, p.FixedMetaPosition.X] = Matrix[exFixedPoint.Y, exFixedPoint.X];
                Matrix[exFixedPoint.Y, exFixedPoint.X] = temp;

                //更新该棋子的三个位置属性，即FloatMetaPosition、FixedMetaPosition、OldMetaPosition
                p.FloatMetaPosition = new Point(BOARD_META_WIDTH - 1 - p.FloatMetaPosition.X, BOARD_META_HEIGHT - 1 - p.FloatMetaPosition.Y);
                p.FixedMetaPosition = exFixedPoint;
                p.OldMetaPosition = new Point(BOARD_META_WIDTH - 1 - p.OldMetaPosition.X, BOARD_META_HEIGHT - 1 - p.OldMetaPosition.Y);

                //更新历史堆栈HistorySteps。
                //注意：此处不包含被吃掉的棋子的历史轨迹，
                //需要在ExchangeEatenPieces方法中更新被吃掉的棋子的历史轨迹
                p.HistorySteps.UpdateStack<PieceInfo>(pt =>
                {
                    return new PieceInfo { Pos = new Point(BOARD_META_WIDTH - 1 - pt.Pos.X, BOARD_META_HEIGHT - 1 - pt.Pos.Y), EatenTag = pt.EatenTag };
                });
                
                //更新吃过的棋子位置
                p.ExchangeEatenPieces();

                //更新该棋子的Side
                p.Side = p.Side == SIDE.UP ? SIDE.DOWN : SIDE.UP;
            }

            //更新当前棋盘中，红方在上边还是下边
            isRedUp = !isRedUp;
        }

        /// <summary>
        /// 红方是否在棋盘的上方
        /// </summary>
        private bool isRedUp = true;

        /// <summary>
        /// 初始化棋子，默认红方在上边、黑方在下边
        /// </summary>
        private void InitPieces()
        {
            if (Pieces != null)
                Pieces.Clear();

            //马
            Pieces.Add(new Knights(new Point(1, 0)));
            Pieces.Add(new Knights(new Point(7, 0)));
            Pieces.Add(new Knights(new Point(1, 9), TEAM.BLACK, SIDE.DOWN));
            Pieces.Add(new Knights(new Point(7, 9), TEAM.BLACK, SIDE.DOWN));

            //車
            Pieces.Add(new Rooks(new Point(0, 0)));
            Pieces.Add(new Rooks(new Point(8, 0)));
            Pieces.Add(new Rooks(new Point(0, 9), TEAM.BLACK, SIDE.DOWN));
            Pieces.Add(new Rooks(new Point(8, 9), TEAM.BLACK, SIDE.DOWN));

            //炮
            Pieces.Add(new Cannons(new Point(1, 2)));
            Pieces.Add(new Cannons(new Point(7, 2)));
            Pieces.Add(new Cannons(new Point(1, 7), TEAM.BLACK, SIDE.DOWN));
            Pieces.Add(new Cannons(new Point(7, 7), TEAM.BLACK, SIDE.DOWN));

            //象
            Pieces.Add(new Elephants(new Point(2, 0)));
            Pieces.Add(new Elephants(new Point(6, 0)));
            Pieces.Add(new Elephants(new Point(2, 9), TEAM.BLACK, SIDE.DOWN));
            Pieces.Add(new Elephants(new Point(6, 9), TEAM.BLACK, SIDE.DOWN));

            //士
            Pieces.Add(new Mandarins(new Point(3, 0)));
            Pieces.Add(new Mandarins(new Point(5, 0)));
            Pieces.Add(new Mandarins(new Point(3, 9), TEAM.BLACK, SIDE.DOWN));
            Pieces.Add(new Mandarins(new Point(5, 9), TEAM.BLACK, SIDE.DOWN));

            //将
            Pieces.Add(new King(new Point(4, 0)));
            Pieces.Add(new King(new Point(4, 9), TEAM.BLACK, SIDE.DOWN));

            //卒
            for (int i = 0; i < 10; i += 2)
            {
                Pieces.Add(new Pawns(new Point(i, 3)));
                Pieces.Add(new Pawns(new Point(i, 6), TEAM.BLACK, SIDE.DOWN));
            }

            if (!isRedUp)
                ExchangeSide();
        }

        /// <summary>
        /// 根据像素坐标，得到索引坐标
        /// </summary>
        /// <param name="pixelX">像素横坐标</param>
        /// <param name="pixelY">像素纵坐标</param>
        /// <returns></returns>
        public static Point GetMetaPosFromPixelPos(float pixelX, float pixelY)
        {
            int x = (int)((pixelX - LINE_WEIGHT / 2) / (LINE_WEIGHT + sW));
            int y = (int)((pixelY - LINE_WEIGHT / 2) / (LINE_WEIGHT + sH));

            //如果超出棋盘边界，则不移动
            if (x >= BOARD_META_WIDTH || x < 0)
                return new Point(-1, -1);//此处不能用Point.Empty表示超出边界，因为Point.Empty等于（0,0），而（0,0）是棋盘上一个普通点
            if (y >= BOARD_META_HEIGHT || y < 0)
                return new Point(-1, -1);//此处不能用Point.Empty表示超出边界，因为Point.Empty等于（0,0），而（0,0）是棋盘上一个普通点
            return new Point(x, y);
        }

        /// <summary>
        /// 由索引坐标得到像素坐标
        /// </summary>
        /// <param name="metaPos">索引坐标</param>
        /// <returns></returns>
        public static Tuple<float,float> GetPixelPosFromMetaPos(Point metaPos)
        {
            Tuple<float, float> r = new Tuple<float, float>(
                metaPos.X * (LINE_WEIGHT + sW) + LINE_WEIGHT / 2 + sW / 2,
                metaPos.Y * (LINE_WEIGHT + sH) + LINE_WEIGHT / 2 + sH / 2
                );
            return r;
        }

        /// <summary>
        /// 绘制棋盘
        /// </summary>
        /// <param name="g">Graphics对象</param>
        public void Draw(Graphics g)
        {
            Pen pen = new Pen(Color.Black, LINE_WEIGHT);

            //绘制外边框
            g.DrawRectangle(pen,
                LINE_WEIGHT / 2 + sW / 2,
                LINE_WEIGHT / 2 + sH / 2,
                BoradWidth - LINE_WEIGHT - sW,
                BoardHeight - LINE_WEIGHT - sH);

            //绘制纵向线段
            for (float i = sW / 2 + LINE_WEIGHT / 2; i < BoradWidth; i += (sW + LINE_WEIGHT))
            {
                g.DrawLine(pen,
                    i,
                    sH / 2 + LINE_WEIGHT / 2,
                    i, 
                    (LINE_WEIGHT + sH) * (BOARD_META_HEIGHT / 2 - 1) + sH / 2 + LINE_WEIGHT / 2);
                g.DrawLine(pen,
                    i, 
                    (LINE_WEIGHT + sH) * (BOARD_META_HEIGHT / 2) + sH / 2 + LINE_WEIGHT / 2, 
                    i, 
                    BoardHeight - sH / 2 - LINE_WEIGHT / 2);
            }

            //绘制横向线段
            for (float j = sH / 2 + LINE_WEIGHT / 2; j < BoardHeight; j += (sH + LINE_WEIGHT))
            {
                g.DrawLine(pen, sW / 2 + LINE_WEIGHT / 2, j, BoradWidth - sW / 2 - LINE_WEIGHT / 2, j);
            }

            //绘制九宫格
            DrawJiuGongGe(g, pen);

            //绘制楚河汉界字样
            DrawChuHeHanJie(g);
            
            //绘制炮位
            DrawPaoWei(g, pen);

            //绘制兵位
            DrawZuWei(g, pen);

            //绘制棋子
            DrawPieces(g);

            //显示矩阵
            //ShowMatirx(g);
        }

        /// <summary>
        /// 显示棋盘矩阵
        /// </summary>
        /// <param name="g">Graphics对象</param>
        private static void ShowMatirx(Graphics g)
        {
            for (int i = 0; i < BOARD_META_HEIGHT; i++)
                for (int j = 0; j < BOARD_META_WIDTH; j++)
                {
                    var pos = GetPixelPosFromMetaPos(new Point(j, i));
                    g.DrawString(Matrix[i, j].ToString(), new Font("宋体", 16), Brushes.SeaGreen, pos.Item1, pos.Item2);
                }
        }

        /// <summary>
        /// 绘制棋子
        /// </summary>
        /// <param name="g">Graphics对象</param>
        private void DrawPieces(Graphics g)
        {
            foreach (var p in Pieces)
            {
                p.Draw(g);
            }
        }

        /// <summary>
        /// 根据索引坐标，得到棋子集合
        /// </summary>
        /// <param name="metaPtX">索引横坐标</param>
        /// <param name="metaPtY">索引纵坐标</param>
        /// <returns></returns>
        public static List<ChessPiece> GetPieceFromMetaPos(int metaPtX,int metaPtY)
        {
            List<ChessPiece> r = new List<ChessPiece>();
            foreach (var pp in Pieces)
            {
                if (pp.FixedMetaPosition.X == metaPtX && pp.FixedMetaPosition.Y == metaPtY)
                    r.Add(pp);
            }
            return r;

            #region 方法二
            //var p = GetPixelPosFromMetaPos(metaPt);
            //return GetPieceFromPixelPos(new Point((int)p.Item1, (int)p.Item2)); 
            #endregion
        }

        /// <summary>
        /// 根据像素坐标，得到棋子集合
        /// </summary>
        /// <param name="pixelPt">像素坐标</param>
        /// <returns></returns>
        public static List<ChessPiece> GetPieceFromPixelPos(Point pixelPt)
        {
            List<ChessPiece> r = new List<ChessPiece>();
            foreach (var p in Pieces)
            {
                if (p.IsMouseEnter(pixelPt))
                {
                    r.Add(p);
                }
            }
            return r;
        }

        /// <summary>
        /// 绘制兵卒位
        /// </summary>
        /// <param name="g">Graphics对象</param>
        /// <param name="pen">画笔</param>
        private void DrawZuWei(Graphics g, Pen pen)
        {
            for (int i = 0; i < 10; i += 2)
            {
                g.DrawRectangle(Pens.Gray,
                    (LINE_WEIGHT + sW) * i + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                    (LINE_WEIGHT + sH) * 3 + LINE_WEIGHT / 2 - sH / 4 + sH / 2, 
                    sW / 2, 
                    sH / 2);
                g.DrawRectangle(Pens.Gray,
                    (LINE_WEIGHT + sW) * i + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                    (LINE_WEIGHT + sH) * 6 + LINE_WEIGHT / 2 - sH / 4 + sH / 2, 
                    sW / 2, 
                    sH / 2);
            }
        }

        /// <summary>
        /// 绘制九宫格
        /// </summary>
        /// <param name="g">Graphics对象</param>
        /// <param name="pen">画笔</param>
        private void DrawJiuGongGe(Graphics g, Pen pen)
        {
            g.DrawLine(pen,
                (LINE_WEIGHT + sW) * 3 + LINE_WEIGHT / 2 + sW / 2,
                LINE_WEIGHT / 2 + sH / 2,
                (LINE_WEIGHT + sW) * 5 + LINE_WEIGHT / 2 + sW / 2,
                (LINE_WEIGHT + sH) * 2 + LINE_WEIGHT / 2 + sH / 2);
            g.DrawLine(pen,
                (LINE_WEIGHT + sW) * 5 + LINE_WEIGHT / 2 + sW / 2,
                LINE_WEIGHT / 2 + sH / 2,
                (LINE_WEIGHT + sW) * 3 + LINE_WEIGHT / 2 + sW / 2,
                (LINE_WEIGHT + sH) * 2 + LINE_WEIGHT / 2 + sH / 2);

            g.DrawLine(pen,
                (LINE_WEIGHT + sW) * 3 + LINE_WEIGHT / 2 + sW / 2,
                (LINE_WEIGHT + sH) * 7 + LINE_WEIGHT / 2 + sH / 2,
                (LINE_WEIGHT + sW) * 5 + LINE_WEIGHT / 2 + sW / 2,
                BoardHeight - LINE_WEIGHT / 2 - sH / 2);
            g.DrawLine(pen,
                (LINE_WEIGHT + sW) * 5 + LINE_WEIGHT / 2 + sW / 2,
                (LINE_WEIGHT + sH) * 7 + LINE_WEIGHT / 2 + sH / 2,
                (LINE_WEIGHT + sW) * 3 + LINE_WEIGHT / 2 + sW / 2,
                BoardHeight - LINE_WEIGHT / 2 - sH / 2);
        }

        /// <summary>
        /// 绘制“楚河汉界”字样
        /// </summary>
        /// <param name="g">Graphics对象</param>
        private void DrawChuHeHanJie(Graphics g)
        {
            Font font = new Font("宋体", 30, GraphicsUnit.Pixel);
            string chuHeHanJieString = "楚河          汉界";
            var strSize = g.MeasureString(chuHeHanJieString, font);
            g.DrawString(chuHeHanJieString, font, Brushes.Black, (BoradWidth - strSize.Width) / 2, (BoardHeight - strSize.Height) / 2 + 5);
        }

        /// <summary>
        /// 绘制炮位
        /// </summary>
        /// <param name="g">Graphics对象</param>
        /// <param name="pen">画笔</param>
        private void DrawPaoWei(Graphics g,Pen pen)
        {
            g.DrawRectangle(Pens.Gray,
                (LINE_WEIGHT + sW) * 1 + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                (LINE_WEIGHT + sH) * 2 + LINE_WEIGHT / 2 - sH / 4 + sH / 2,
                sW / 2,
                sH / 2);
            g.DrawRectangle(Pens.Gray,
                (LINE_WEIGHT + sW) * 7 + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                (LINE_WEIGHT + sH) * 2 + LINE_WEIGHT / 2 - sH / 4 + sH / 2, 
                sW / 2, 
                sH / 2);
            g.DrawRectangle(Pens.Gray,
                (LINE_WEIGHT + sW) * 1 + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                (LINE_WEIGHT + sH) * 7 + LINE_WEIGHT / 2 - sH / 4 + sH / 2,
                sW / 2, 
                sH / 2);
            g.DrawRectangle(Pens.Gray,
                (LINE_WEIGHT + sW) * 7 + LINE_WEIGHT / 2 - sW / 4 + sW / 2,
                (LINE_WEIGHT + sH) * 7 + LINE_WEIGHT / 2 - sH / 4 + sH / 2,
                sW / 2, 
                sH / 2);
        }
    }
}
